
/*
 * Created on 13 avr. 2005
 * Group of SIMPACK 2004-2005
 * Coded by TRAN Thuy Tien & NGOUNDJI Gerard 
 */
package communication.thirdLayer;

import objects.Station;
import java.util.Vector;
import communication.pack.*;
import java.util.*;
import java.io.Serializable;

/**
 * @author SIMPACK 2005 This class define the network part of a station. Router
 *         gives its station the knowledge of the network topology, and helps
 *         its station to send or to receive packages of data from another
 *         station. Router also helps to spread many control-typed packages in
 *         the network.
 */
public abstract class Router implements Serializable {

    /**
     * The station who possesses this router
     */
    private Station station;

    /**
     * Sequence number is used to distinguish a new route and an old one.
     * Sequence counter of the router will be incremented whenever a package of
     * one of the following types is sent: RouteRequest or RouteReply in the
     * case of AODV TCPackage in the case of OLSR
     */
    private int sequenceCounter;

    /**
     * Request number is used to identify a request in the network. Request
     * counter of the router will be incremented whenever a package of any type
     * is sent
     */
    private int requestCounter;

    /**
     * All the package of type RouteRequest which is received by the station is
     * saved in the historyTable. So that the station can recognise a double of
     * one of the package that it has received before
     */
    private Vector historyTable;

    public static final int HistoryTableSize = 50;

    /**
     * receivedData is used to stock all the package that is not yet assembled
     * key: name of a file; value: size of received data of this file
     */
    private HashMap receivedData;

    /**
     * fullyReceivedData is used to stock all the files that have been totally
     * received key: name of a file; value: size of this file
     */
    private HashMap fullyReceivedData;

    /**
     * Routing Table presents the knowledge of the router about the topology of
     * the network. It is a HashMap of objects as RoutingTableEntry.
     */
    private Vector routingTable;

    /**
     * This Vector stocks all the packages that need to be sent but haven't been
     * sent yet. They will be sent when the station receive RouteReply
     */
    private Vector dataToBeSent;

    // -----------------------------------------------------------------
    /**
     * Constructor of a router from a Station where it belongs
     */
    public Router(Station s) {
	this.station = s;
	this.sequenceCounter = 0;
	this.requestCounter = 0;
	this.historyTable = new Vector();
	this.receivedData = new HashMap();
	this.fullyReceivedData = new HashMap();
	this.routingTable = new Vector();
	this.dataToBeSent = new Vector();
    }

	

    /**
     * Display a router in a string
     */
    public String toString() {
	String router = "routeur : " + station.getName() + "\n"
	    + "numero de seq : " + sequenceCounter + "\n"
	    + "numero de requete : " + requestCounter;
	return router;
    }

    /**
     * Default constructor of a router
     */
    public Router() {
	this.station = null;
	this.sequenceCounter = 0;
	this.requestCounter = 0;
	this.historyTable = new Vector();
	this.receivedData = new HashMap();
	this.fullyReceivedData=new HashMap();
	this.routingTable = new Vector();
    }

    /*-----Fonctions d'acces----*/

    public int getNoSequence() {
	return this.sequenceCounter;
    }

    public int getNoRequest() {
	return this.requestCounter;
    }

    /**
     * @return life time of a package
     */
    public int getTTL() {
	return SMPackage.TTLFactor
	    * this.getStation().getSimulator().numberOfMobiles();
    }

    /**
     * Increment the counter of sequence number of the router
     */
    public void incrementSequenceCounter() {
	//cette methode est a revoir (mecanisme de retour a 0 
	//quand le compteur depasse une ceraine valeur)
	this.sequenceCounter++;
    }

    /**
     * Increment the counter of request number of the router
     */
    public void incrementRequesCounter() {
	//cette methode est a revoir (mecanisme de retour a 0 
	//quand le compteur depasse une ceraine valeur)
	this.requestCounter++;
    }

    public Station getStation() {
	return this.station;
    }

    public void addDataPackage(DataPackage p) {
	this.dataToBeSent.addElement(p);
    }

    public void removeDataPackage(DataPackage p) {
	this.dataToBeSent.removeElement(p);
    }

    public Vector getDataToBeSent() {
	return this.dataToBeSent;
    }

    public Vector getRoutingTable() {
	return this.routingTable;
    }
    
     

    /**
     * @param p the RouteRequest package
     * @return true if the station has received this package RouteRequest before  
     */
    public boolean isDuplicated(RouteRequest p) {
	if (this.historyTable.isEmpty())
	    return false;
	else{
	    boolean trouve=false;
	    int i=0;
	    while (i<this.historyTable.size() && (!trouve)){
		RouteRequest r=(RouteRequest)this.historyTable.get(i);
		if(r.getSenderId().equals(p.getSenderId())
		   && r.getRequestId()==p.getRequestId())
		    trouve=true;
		i++;
	    }
	    return trouve;
	}
    }

    /**
     * Treatment of a package p when it arrived to this station from the station transmitter
     * @param p the package who has just arrived
     * @param transmitter
     */
    public abstract void packageTreatment(SMPackage p, Station transmitter);

    /**
     * This function update the routing table by saving the route leading to the
     * primary sender of this package
     * 
     * @param p
     *            the package in treatment
     * @return true in the case the save is successful, only when p is
     *         RouteRequest or RouteReply
     */
    public abstract int routingTableUpdate(SMPackage p, Station transmitter);

    /**
     * add a package in the history table, so that if the station received the same
     * package again, it would know right away
     * @param p
     */
    public void addInHistoryTable(SMPackage p) {
	if (!this.historyTable.contains(p)) {
	    // we conserve the size of the vector historyTable
	    if (this.historyTable.size() >= Router.HistoryTableSize)
		this.historyTable.remove(0);
	    this.historyTable.addElement(p);
	}
    }

    /**
     * @param dest the station of destination
     * @return the RoutingTableEntry which leads to dest
     */

    public RoutingTableEntry search(Station dest) {
	if (!this.getRoutingTable().isEmpty()) {
	    for (int i = 0; i < this.getRoutingTable().size(); i++) {
		RoutingTableEntry entry = (RoutingTableEntry) this
		    .getRoutingTable().elementAt(i);
		if (entry.getDestination() == dest) {
		    return entry;
		}
	    }
	    return null;
	} else
	    return null;
    }

    /**
     * set timer of the RoutingTableEntry which leads to the Station dest to 0
     * 
     * @param dest
     */
    public void razEntry(Station dest) {

	for (int i = 0; i < this.getRoutingTable().size(); i++) {
	    RoutingTableEntry entry = (RoutingTableEntry) this
		.getRoutingTable().elementAt(i);
	    if (entry.getDestination() == dest)
		entry.razTimer();
	}

    }

    /**
     * When a destination is searched, its timer is set to 0
     * 
     * @param dest
     * @return the next Station that needs to send to in order to achieve the
     *         destination If the destination station isn't found in the
     *         routingTable, this method will return null -> CAUTION!!!
     */
    public Station routingTableSearch(Station dest) {
	this.razEntry(dest);
	RoutingTableEntry entry = this.search(dest);
	if (entry != null)
	    return entry.getNextNode();
	else
	    return null;
    }

    /**
     * Sometimes we just need to know if the station dest existe in the routing
     * table, we don't need to know the next node to send it
     * 
     * @param dest
     *            the Station destination
     * @return true if it's present
     */
    public boolean isPresent(Station dest) {
	if (!this.getRoutingTable().isEmpty()) {
	    for (int i = 0; i < this.getRoutingTable().size(); i++) {
		RoutingTableEntry entry = (RoutingTableEntry) this
		    .getRoutingTable().elementAt(i);
		if (entry.getDestination() == dest)
		    return true;
	    }
	    return false;
	} else
	    return false;
    }

    /**
     * Send a package p, all the information necessary such as the destination
     * is stocked in p
     * @param p
     */
    public abstract void send(SMPackage p);

    /**
     * Compare a routingTableEntry with others in the routing table. If this one
     * is new or in the case the route leading to the same destination already
     * exists, it will replace the old one if it's a better route
     * 
     * @param newEntry
     * @return true if the routing table changes
     */
    public boolean addRoutingTableEntry(RoutingTableEntry newEntry) {
	boolean add = false;
	boolean find = false;
	for (int i = 0; i < this.getRoutingTable().size(); i++) {
	    RoutingTableEntry entry = (RoutingTableEntry) this
		.getRoutingTable().elementAt(i);
	    if (entry.getDestination() == newEntry.getDestination()) {
		find = true;
		if (entry.getStepCount() > newEntry.getStepCount()) {
		    add = this.replaceEntry(entry, newEntry);
		} else if (entry.getStepCount() == newEntry.getStepCount()) {
		    if (entry.getSequence() < newEntry.getSequence()) {
			add = this.replaceEntry(entry, newEntry);
		    }
		}
	    }
	}

	if (!find) {
	    this.routingTable.addElement(newEntry);
	    add = true;
	}
	return add;
    }

    /**
     * Remove the entry e in the RoutingTable
     * 
     * @param e
     * @return true if the entry e existed in the routing table and is removed
     *         sucsessfully
     */
    public boolean removeRoutingTableEntry(RoutingTableEntry e) {
	if (this.getRoutingTable().contains(e)) {
	    this.routingTable.remove(e);
	    return true;
	} else
	    return false;
    }

    /**
     * Replace a routingTable entry by a new one
     * 
     * @param entry
     * @param newEntry
     * @return true when it is succeeded (there is no reason that it returns
     *         false, but this boolean is used in another function)
     */
    private boolean replaceEntry(RoutingTableEntry entry,
				 RoutingTableEntry newEntry) {
	this.routingTable.remove(entry);
	this.routingTable.addElement(newEntry);
	return true;
    }

    public HashMap getReceivedData() {
	return this.receivedData;
    }

    public HashMap getFullyReceivedData() {
	return this.fullyReceivedData;
    }

    /**
     * remove a file from the hashMap receivedData
     * @param f
     */	
    public void removeFile(int f){
	Set v=receivedData.keySet();
	Iterator it=v.iterator();
	while(it.hasNext()){
	    Station s=(Station)it.next();
	    HashMap h=(HashMap)this.receivedData.get(s);
	    if (h.containsKey(new Integer(f)))
		h.remove(new Integer(f));
	}
    }
    /**
     * add a new file into the HashMap receivedData
     * @param f number of the file
     * @param s size of the file that the station has received so far
     */
    public void addFile(Station s, int f, int t) {
	if(this.receivedData.containsKey(s)){
	    HashMap h=(HashMap)this.receivedData.get(s);		
	    h.put(new Integer(f), new Integer(t));
	}else{
	    HashMap h=new HashMap();
	    h.put(new Integer(f), new Integer(t));
	    this.receivedData.put(s, h);
	}
    }

    /**
     * add a file that the station has received completely
     * @param f
     * @param s
     */
    public void addCompletedFile(Station s, int f, int t) {
	if(this.fullyReceivedData.containsKey(s)){
	    HashMap h=(HashMap)this.fullyReceivedData.get(s);		
	    h.put(new Integer(f), new Integer(t));
	}else{
	    HashMap h=new HashMap();
	    h.put(new Integer(f), new Integer(t));
	    this.fullyReceivedData.put(s, h);
	}
    }

    /**
     * this method is called in order to display the router's routingTable
     */
    public void displayRoutingLayer() {
	System.out.println("  ######## affichage table de routage de "
			   + this.getStation().getName() + "  #######");
	for (int i = 0; i < routingTable.size(); i++) {
	    System.out.println(((RoutingTableEntry) routingTable.elementAt(i))
			       .toString());
	}
	System.out.println("  ########## fin affichage table de routage de "
			   + this.getStation().getName() + "  #######\n");
    }

    /**
     * Print the routing table
     * @return 
     */
    public String routingTableToString(){
	String s=this.getStation().getName()+":\n";
	for(int i=0;i<this.getRoutingTable().size(); i++){
	    s+=" - " + ((RoutingTableEntry)this.getRoutingTable().get(i)).toString() + (i == this.getRoutingTable().size()-1 ? "" : "\n");

	}
	return s;
    }

    // fin modif

    /**
     * This method exclude all the RoutingTableEntry that uses the station in
     * parameter
     * 
     * @param line
     *            a station in the neighborhood
     */
    public void exludeNeighbor(Station line) {

	RoutingTableEntry entry = this.search(line);
	// si line etait un de mes voisins
	if (entry != null) {
	    //System.out.println(entry.toString());
	    if (entry.getStepCount() == 1) {

		Vector rem = new Vector();
		for (int i = 0; i < this.getRoutingTable().size(); i++) {
		    RoutingTableEntry e = (RoutingTableEntry) this
			.getRoutingTable().get(i);
		    //System.out.println(e.toString());
		    if (e.getNextNode().equals(line)) {
			rem.add(e);
		    }
		}
		this.getRoutingTable().removeAll(rem);
	    }
	}
    }

    /**
     * Treatment when this station failed a communication with dest
     * @param dest the station destination that this station failed to communicate with
     */
    public abstract void failedDestination(Station dest);

    /**
     * Print all the files that are received completely
     * @return
     */
    public String fullyReceivedDataToString(){
	String str="";
	Set m=this.getFullyReceivedData().keySet();
	Iterator it=m.iterator();
	while(it.hasNext()){
	    Station s=(Station)it.next();
	    HashMap h=(HashMap)this.getFullyReceivedData().get(s);
	    Set k=h.keySet();
	    Iterator it1=k.iterator();
	    str+=" -from "+s.getName()+": \n";
	    while(it1.hasNext()){
		Integer file=(Integer)it1.next();
		int size=((Integer)h.get(file)).intValue();
		str+=size+" bytes; \n";
	    }
	}
	return str;
    }
    /**
     * Print all the files that are received partially
     * @return
     */
	
    public String receivedDataToString(){
	String str="";
	Set m=this.getReceivedData().keySet();
	Iterator it=m.iterator();
	while(it.hasNext()){
	    Station s=(Station)it.next();
	    HashMap h=(HashMap)this.getReceivedData().get(s);
	    Set k=h.keySet();
	    Iterator it1=k.iterator();
	    str+=" -from "+s.getName()+": \n";
	    while(it1.hasNext()){
		Integer file=(Integer)it1.next();
		int size=((Integer)h.get(file)).intValue();
		str+=size+" bytes; \n";
	    }
	}
	return str;
    }
}
